/**
 * Copyright (c) 2011-2023, James Zhan 詹波 (jfinal@126.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jfinal.template.expr;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import com.jfinal.kit.JavaKeyword;
import com.jfinal.template.stat.CharTable;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParaToken;
import com.jfinal.template.stat.ParseException;

/**
 * ExprLexer
 */
class ExprLexer {

    static final char EOF = (char)-1;
    static final JavaKeyword javaKeyword = new JavaKeyword();
    static final Pattern DOUBLE_QUOTES_PATTERN = Pattern.compile("\\\\\"");
    static final Pattern SINGLE_QUOTES_PATTERN = Pattern.compile("\\\\'");

    char[] buf;
    int state = 0;
    int lexemeBegin = 0;
    int forward = 0;
    int beginRow = 1;
    int forwardRow = 1;
    List<Tok> tokens = new ArrayList<Tok>();
    Location location;

    public ExprLexer(ParaToken paraToken, Location location) {
        this.location = location;
        StringBuilder content = paraToken.getContent();
        beginRow = paraToken.getRow();
        forwardRow = beginRow;
        if (content == null) {
            buf = new char[]{EOF};
            return ;
        }
        int len = content.length();
        buf = new char[len + 1];
        content.getChars(0, content.length(), buf, 0);
        buf[len] = EOF;
    }

    public List<Tok> scan() {
        while (peek() != EOF) {
            skipBlanks();
            lexemeBegin = forward;
            beginRow = forwardRow;
            if (scanId()) {
                continue ;
            }
            if (scanOperator()) {
                continue ;
            }
            if (scanString()) {
                continue ;
            }
            if (scanNumber()) {
                continue ;
            }

            if (peek() != EOF) {
                throw new ParseException("Expression not support the char: '" + peek() + "'", location);
            }
        }
        return tokens;
    }

    /**
     * 扫描 ID true false null
     */
    boolean scanId() {
        if (state != 0) {
            return false;
        }

        if (!CharTable.isLetter(peek())) {
            return fail();
        }

        while (CharTable.isLetterOrDigit(next())) {
            ;
        }
        String id = subBuf(lexemeBegin, forward - 1).toString();
        if ("true".equals(id)) {
            addToken(new Tok(Sym.TRUE, id, beginRow));
        } else if ("false".equals(id)) {
            addToken(new Tok(Sym.FALSE, id, beginRow));
        } else if ("null".equals(id)) {
            addToken(new Tok(Sym.NULL, id, beginRow));
        } else if (CharTable.isBlankOrLineFeed(peek()) && javaKeyword.contains(id)) {
            throw new ParseException("Identifier can not be java keyword : " + id, location);
        } else {
            addToken(new Tok(Sym.ID, id, beginRow));
        }
        return prepareNextScan();
    }

    /**
     * + - * / % ++ --
     * = == != < <= > >=
     * ! && ||
     * ? ?? ?.
     * . .. : :: , ;
     * ( ) [ ] { }
     */
    boolean scanOperator() {
        if (state != 100) {
            return false;
        }

        Tok tok;
        switch (peek()) {
        case '+':		// + - * / % ++ --
            if (next() == '+') {
                tok = new Tok(Sym.INC, beginRow);
                next();
            } else {
                tok = new Tok(Sym.ADD, beginRow);
            }
            return ok(tok);
        case '-':
            if (next() == '-') {
                tok = new Tok(Sym.DEC, beginRow);
                next();
            } else {
                tok = new Tok(Sym.SUB, beginRow);
            }
            return ok(tok);
        case '*':
            tok = new Tok(Sym.MUL, beginRow);
            next();
            return ok(tok);
        case '/':
            tok = new Tok(Sym.DIV, beginRow);
            next();
            return ok(tok);
        case '%':
            tok = new Tok(Sym.MOD, beginRow);
            next();
            return ok(tok);
        case '=':		// = == != < <= > >=
            if (next() == '=') {
                tok = new Tok(Sym.EQUAL, beginRow);
                next();
            } else {
                tok = new Tok(Sym.ASSIGN, beginRow);
            }
            return ok(tok);
        case '!':
            if (next() == '=') {
                tok = new Tok(Sym.NOTEQUAL, beginRow);
                next();
            } else {
                tok = new Tok(Sym.NOT, beginRow);
            }
            return ok(tok);
        case '<':
            if (next() == '=') {
                tok = new Tok(Sym.LE, beginRow);
                next();
            } else {
                tok = new Tok(Sym.LT, beginRow);
            }
            return ok(tok);
        case '>':
            if (next() == '=') {
                tok = new Tok(Sym.GE, beginRow);
                next();
            } else {
                tok = new Tok(Sym.GT, beginRow);
            }
            return ok(tok);
        case '&':		// ! && ||
            if (next() == '&') {
                tok = new Tok(Sym.AND, beginRow);
                next();
            } else {
                throw new ParseException("Unsupported operator: '&'", location);
            }
            return ok(tok);
        case '|':
            if (next() == '|') {
                tok = new Tok(Sym.OR, beginRow);
                next();
            } else {
                throw new ParseException("Unsupported operator: '|'", location);
            }
            return ok(tok);
        case '?':		// ? ?? ?.
            char c = next();
            if (c == '?') {
                tok = new Tok(Sym.NULL_SAFE, beginRow);
                next();
            } else if (c == '.') {
                tok = new Tok(Sym.OPTIONAL_CHAIN, beginRow);
                next();
            } else {
                tok = new Tok(Sym.QUESTION, beginRow);
            }
            return ok(tok);
        case '.':		// . .. : :: , ;
            if (next() == '.') {
                tok = new Tok(Sym.RANGE, beginRow);
                next();
            } else {
                tok = new Tok(Sym.DOT, ".", beginRow);
            }
            return ok(tok);
        case ':':
            if (next() == ':') {
                tok = new Tok(Sym.STATIC, beginRow);
                next();
            } else {
                tok = new Tok(Sym.COLON, beginRow);
            }
            return ok(tok);
        case ',':
            tok = new Tok(Sym.COMMA, beginRow);
            next();
            return ok(tok);
        case ';':
            tok = new Tok(Sym.SEMICOLON, beginRow);
            next();
            return ok(tok);
        case '(':		// ( ) [ ] { }
            tok = new Tok(Sym.LPAREN, beginRow);
            next();
            return ok(tok);
        case ')':
            tok = new Tok(Sym.RPAREN, beginRow);
            next();
            return ok(tok);
        case '[':
            tok = new Tok(Sym.LBRACK, beginRow);
            next();
            return ok(tok);
        case ']':
            tok = new Tok(Sym.RBRACK, beginRow);
            next();
            return ok(tok);
        case '{':
            tok = new Tok(Sym.LBRACE, beginRow);
            next();
            return ok(tok);
        case '}':
            tok = new Tok(Sym.RBRACE, beginRow);
            next();
            return ok(tok);
        default :
            return fail();
        }
    }

    boolean ok(Tok tok) {
        tokens.add(tok);
        return prepareNextScan();
    }

    boolean scanString() {
        if (state != 200) {
            return false;
        }

        char quotes = peek();
        if (quotes != '"' && quotes != '\'') {
            return fail();
        }

        for (char c=next(); true; c=next()) {
            if (c == quotes) {
                if (buf[forward - 1] != '\\') {	// 前一个字符不是转义字符
                    StringBuilder sb = subBuf(lexemeBegin + 1, forward -1);
                    String str;
                    if (sb != null) {
                        if (quotes == '"') {
                            str = DOUBLE_QUOTES_PATTERN.matcher(sb).replaceAll("\"");
                        } else {
                            str = SINGLE_QUOTES_PATTERN.matcher(sb).replaceAll("'");
                        }
                    } else {
                        str = "";
                    }

                    Tok tok = new Tok(Sym.STR, str, beginRow);
                    addToken(tok);
                    next();
                    return prepareNextScan();
                } else {
                    continue ;
                }
            }

            if (c == EOF) {
                throw new ParseException("Expression error, the string not ending", location);
            }
        }
    }

    boolean scanNumber() {
        if (state != 300) {
            return false;
        }

        char c = peek();
        if (!CharTable.isDigit(c)) {
            return fail();
        }

        int numStart = lexemeBegin;				// forward;
        int radix = 10;							// 10 进制
        if (c == '0') {
            c = next();
            if (c == 'X' || c == 'x') {
                radix = 16;						// 16 进制
                c = next();
                numStart = numStart + 2;
            } else if (c != '.') {
                radix = 8;						// 8 进制
                // numStart = numStart + 1;		// 8 进制不用去掉前缀 0，可被正确转换，去除此行便于正确处理数字 0
            }
        }

        c = skipDigit(radix);
        Sym sym = null;
        if (c == '.') {							// 以 '.' 字符结尾是合法的浮点数
            next();
            if (peek() == '.' ||				// 处理 [0..9] 这样的表达式
                CharTable.isLetter(peek())) {	// 处理 123.toInt() 这样的表达式，1.2.toInt() 及 1D.toInt() 可正常处理
                StringBuilder n = subBuf(numStart, forward - 2);
                if (n == null /* && radix == 16 */) {
                    // 16 进制数格式错误，前缀 0x 后缺少 16 进制数字(16 进制时 numStart 已增加了 2， n 为 null 必是 16 进制解析出错)
                    throw new ParseException("Error hex format", location);
                }
                NumTok tok = new NumTok(Sym.INT, n.toString(), radix, false, location);
                addToken(tok);
                retract(1);
                return prepareNextScan();
            }

            sym = Sym.DOUBLE;					// 浮点型默认为 double
            c = skipDigit(radix);
        }

        boolean isScientificNotation = false;
        if (c == 'E' || c == 'e') {				// scientific notation 科学计数法
            c = next();
            if (c == '+' || c == '-') {
                c = next();
            }
            if (!CharTable.isDigit(c)) {
                // 科学计数法后面缺少数字
                throw new ParseException("Error scientific notation format", location);
            }
            isScientificNotation = true;
            sym = Sym.DOUBLE;					// 科学计数法默认类型为 double

            c = skipDecimalDigit();				// 科学计数法的指数部分是十进制
        }

        StringBuilder num;
        if (c == 'L' || c == 'l') {
            if (sym == Sym.DOUBLE) {
                // 浮点类型不能使用 'L' 或 'l' 后缀
                throw new ParseException("Error float format", location);
            }
            sym = Sym.LONG;
            next();
            num = subBuf(numStart, forward - 2);
        } else if (c == 'F' || c == 'f') {
            sym = Sym.FLOAT;
            next();
            num = subBuf(numStart, forward - 2);
        } else if (c == 'D' || c == 'd') {
            sym = Sym.DOUBLE;
            next();
            num = subBuf(numStart, forward - 2);
        } else {
            if (sym == null) {
                sym = Sym.INT;
            }
            num = subBuf(numStart, forward - 1);
        }
        if (errorFollow()) {
            // "错误的表达式元素 : " + num + peek()
            throw new ParseException("Error expression: " + num + peek(), location);
        }
        if (num == null /* && radix == 16 */) {
            // 16 进制数格式错误，前缀 0x 后缺少 16 进制数字
            throw new ParseException("Error hex format", location);
        }

        NumTok tok = new NumTok(sym, num.toString(), radix, isScientificNotation, location);
        addToken(tok);
        return prepareNextScan();
    }

    boolean errorFollow() {
        char c = peek();
        return CharTable.isLetterOrDigit(c) || c == '"' || c == '\'';
    }

    char skipDigit(int radix) {
        if (radix == 10) {
            return skipDecimalDigit();
        } else if (radix == 16) {
            return skipHexadecimalDigit();
        } else {
            return skipOctalDigit();
        }
    }

    char skipDecimalDigit() {
        char c = peek();
        for (; CharTable.isDigit(c);) {
            c = next();
        }
        return c;
    }

    char skipHexadecimalDigit() {
        char c = peek();
        for (; CharTable.isHexadecimalDigit(c);) {
            c = next();
        }
        return c;
    }

    char skipOctalDigit() {
        char c = peek();
        for (; CharTable.isOctalDigit(c);) {
            c = next();
        }
        return c;
    }

    boolean fail() {
        forward = lexemeBegin;
        forwardRow = beginRow;

        if (state < 100) {
            state = 100;
        } else if (state < 200) {
            state = 200;
        } else if (state < 300) {
            state = 300;
        }
        return false;
    }

    char next() {
        if (buf[forward] == '\n') {
            forwardRow++;
        }
        return buf[++forward];
    }

    char peek() {
        return buf[forward];
    }

    /**
     * 表达式词法分析需要跳过换行与回车
     */
    void skipBlanks() {
        while(CharTable.isBlankOrLineFeed(buf[forward])) {
            next();
        }
    }

    StringBuilder subBuf(int start, int end) {
        if (start > end) {
            return null;
        }
        StringBuilder ret = new StringBuilder(end - start + 1);
        for (int i=start; i<=end; i++) {
            ret.append(buf[i]);
        }
        return ret;
    }

    boolean prepareNextScan() {
        state = 0;
        lexemeBegin = forward;
        beginRow = forwardRow;
        return true;
    }

    void addToken(Tok tok) {
        tokens.add(tok);
    }

    void retract(int n) {
        for (int i=0; i<n; i++) {
            forward--;
            if (buf[forward] == '\n') {
                forwardRow--;
            }
        }
    }
}





